/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: hello_world.c
 *
 * Purpose: implementation hello world test, a command can create sercice or client.
 *
 * Developer:
 *   wen.gu , 2022-03-6
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
//#include <string.h>
#include "stl_arg_parse.h"

#include "cbus/bus/cbus_api.h"

#include "cbus/core/cbus_thread.h"
#include "cbus/core/cbus_string.h"


#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define SRV_METHOD_SAY_HELLO 1

#define SRV_EVENT_SAY_HELLO 2

#define SRV_INTERFACE_VER 2
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/



struct _my_srv
{
    const char* url;
    cbus_service_t srv;
};

struct _my_client
{
    const char* url;
    cbus_client_t client;
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static void say_hello(cbus_message_t message)
{

    LOGE("receive mthod call: say_hello(method id: %d), the param content: %s , from client : %p\n", 
        cbus_message_get_message_id(message), (cbus_message_get_payload_size(message) > 0) ? (const char*)cbus_message_get_payload(message) : "",
        (GS64)cbus_message_get_endpoint_id(message));
    struct _my_srv* my_srv = (struct _my_srv*)cbus_message_get_opaque(message);

    const char* response_content = "I'm a service ,my name is: ";
    GU32 content_len = strlen(response_content);
    GU32 url_len = strlen(my_srv->url);
    GU32 response_len = content_len + url_len + 1; /** + 1 is for the '\0' of a string */
    
    cbus_message_t response_message = cbus_message_alloc_response(message, response_len);

    if (!response_message)
    {
        LOGE("alloc response message failed\n");
        GErrc ret = cbus_service_method_send_response_error(my_srv->srv, message, G_ErrInsufficientResources);
        if (G_OK != ret)
        {
            LOGE("send response error failed(0x%08x)\n", ret);
        }
        return ;
    }

    GBUF data_buf = cbus_message_get_payload(response_message);
    memcpy(data_buf, response_content, content_len);
    memcpy(data_buf + content_len, my_srv->url, url_len + 1);

    GErrc ret = cbus_service_method_send_response(my_srv->srv, response_message);

    if (G_OK != ret)
    {
        LOGE("send response error failed(0x%08x)\n", ret);
    }

    cbus_message_free(response_message);
}

static void init_service(const char* url)
{
    struct _my_srv my_srv;
    
    const GSTR url_list[] = {
        (const GSTR)url,
        NULL
    } ;
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    const char* turl = cbus_str_dup("url");
    LOGD("[%s.%d]===>:%s\n", __FUNCTION__, __LINE__, turl);
    
    GErrc ret = cbus_service_create(&my_srv.srv, url_list);

    if (ret != G_OK)
    {
        LOGE("create service(%s) failed(0x%08x)\n", url, ret);
        return ;
    }
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    cbus_service_t srv = my_srv.srv;
    my_srv.url = url;

    ret = cbus_service_method_register(srv, SRV_METHOD_SAY_HELLO, say_hello, &my_srv);

    if (G_OK != ret)
    {
        LOGE("register method(%d) failed(0x%08x) for srv(%s)\n", SRV_METHOD_SAY_HELLO, ret, url);
        cbus_service_destroy(srv);
        return ;
    }

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    ret = cbus_service_start(srv);

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    if (G_OK != ret)
    {
        LOGE("start(offer) service(%s) failed(0x%08x)\n", url, ret);
        cbus_service_destroy(srv); 
        LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        return;
    }

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    const char* my_word = "a hello come from the service";
    uint32_t word_len = strlen(my_word) + 1; /** +1 to include the '\0' of string */

    cbus_message_t hello_event = cbus_message_alloc(word_len);

    if (!hello_event)
    {
        LOGE("alloc event message failed\n");
        cbus_service_destroy(srv);
        return ;
    }

    memcpy(cbus_message_get_payload(hello_event), my_word, word_len);
    cbus_message_set_message_type(hello_event, CBUS_MSG_TYPE_Event);
    cbus_message_set_message_id(hello_event, SRV_EVENT_SAY_HELLO);

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
#if 1    
    while (1)
    {
        cbus_thread_sleep(500);
        ret = cbus_service_event_notify(srv, hello_event);

        if (G_OK != ret)
        {
            if (ret != G_ErrNoSubscriber)
            {
                LOGE("service notify event failed(0x%08x)\n", ret);
                break;
            }            
        }
    }
#else
    while(1)
    {
        cbus_thread_sleep(500);
    }
#endif
    cbus_buffer_free(hello_event);
    cbus_service_destroy(srv);

}

static void client_on_event(cbus_message_t event_data)
{
    struct _my_client* my_client = (struct _my_client*)cbus_message_get_opaque(event_data);
    if (cbus_message_get_payload_size(event_data) == 0)
    {
        LOGE("receive event(id:%d) fomr(url:%s), but haven't conent\n", cbus_message_get_message_id(event_data), my_client->url);
        return ;
    }

    LOGI("receive event(id: %d) from(url: %s), the content is: %s\n", 
    cbus_message_get_message_id(event_data), my_client->url, cbus_message_get_payload(event_data));
}

static void client_on_response(cbus_message_t response_message)
{
    struct _my_client* my_client = (struct _my_client*)cbus_message_get_opaque(response_message);

    if (cbus_message_get_payload_size(response_message) == 0)
    {
        LOGE("receive response(method id:%d) from(url:%s), but haven't conent\n", cbus_message_get_message_id(response_message), my_client->url);
        return ;
    }

    LOGI("receive reponse(method id: %d) from(url: %s), return_code: %d the content is: %s\n", 
        cbus_message_get_message_id(response_message), my_client->url, 
        cbus_message_get_return_code(response_message), cbus_message_get_payload(response_message));

}


static void init_client(const char* url)
{
    struct _my_client my_client;
    my_client.url = url;
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);

    my_client.client = cbus_client_create((const GSTR)url, SRV_INTERFACE_VER);

    if (!my_client.client)
    {
        LOGE("create client with url: %s failed\n", url);
        return ;
    }
    
#if 1
    cbus_client_t client = my_client.client;
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    cbus_client_event_t event_info;
    event_info.event_id = SRV_EVENT_SAY_HELLO;
    event_info.on_event = client_on_event;
    event_info.opaque   = &my_client;

    GErrc ret = cbus_client_subscribe(client, &event_info, 3000);

    if (G_OK != ret)
    {
        LOGE("subcribe event:%d failed(0x%08x)\n", SRV_EVENT_SAY_HELLO, ret);
        cbus_client_destroy(client);
        return ;
    }
    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    const char* param_str = "please say hello";
    GU32 param_len = strlen(param_str) + 1; /** +1 is include '\0' for a string */
    cbus_message_t request_message = cbus_message_alloc(param_len);

    if (!request_message)
    {
        LOGE("alloc request message failed\n");
        cbus_client_destroy(client);
        return ;
    }

    memcpy(cbus_message_get_payload(request_message), param_str, param_len);
    cbus_message_set_message_type(request_message, CBUS_MSG_TYPE_Method_Request);
    cbus_message_set_message_id(request_message, SRV_METHOD_SAY_HELLO);

    LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    while (1)
    {
#if 1        
        cbus_message_t response_message = NULL;
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        ret = cbus_client_request(client, request_message, &response_message, 3000);

        if (G_OK != ret)
        {
            LOGE("request method(id: %d) as sync mode failed(0x%08x)\n", SRV_METHOD_SAY_HELLO, ret);
        }        
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        if (response_message)
        {
            LOGE("got response for method: %d, the response is: %s\n", SRV_METHOD_SAY_HELLO, cbus_message_get_payload(response_message));
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
            cbus_message_free(response_message);
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        }
      
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        ret = cbus_client_request_async(client, request_message, client_on_response, &my_client);

        if (G_OK != ret)
        {
            LOGE("request method(id: %d) as async mode failed(0x%08x)\n", SRV_METHOD_SAY_HELLO, ret);
        }
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
#endif          
        cbus_thread_sleep(1000);
    }
#else
    while(1)
    {
        cbus_thread_sleep(1000);
    }
#endif
    if (my_client.client)
    {
        LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        cbus_client_destroy(my_client.client);
        LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
    }
}


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

int main(int argc, char* argv[])
{
    const char* url = NULL;

    uint64_t is_service = 0; /** 1: service, 0 client */



    stl_arg_option_t options[] = {
        {'s', "service", "if set the argument, indicate init this app as service", STL_ARG_OPTION, STL_ARG_PARAM_NONE, "0", STL_ARG_VALUE_INT, STL_ARG_VALUE_REG(is_service)},
        {STL_ARG_NO_SHORT_NAME, "url", "the url adress", STL_ARG_MANDATORY, STL_ARG_PARAM_NEED, NULL, STL_ARG_VALUE_STR, STL_ARG_VALUE_REG(url)},
    };

    int ret = stl_arg_parse(argc, argv, 1, options, STL_ARG_OPTION_COUNT(options), 1);

    if (ret == 0)
    {
        printf("got is_service: %llu, %llu\n", is_service, *(options[0].value.value_int));
        if (!url)
        {
            stl_arg_show_useage(argv[0], options, STL_ARG_OPTION_COUNT(options), STL_ARG_USEAGE_ADD);
            return 0;
        }

        printf("url: %s, as: %s\n", url ? url : "", is_service ? "service" : "client");

        cbus_api_initialize();

        if (is_service)
        {
            init_service(url);
        }
        else
        {
            init_client(url);
        }

        cbus_api_finish();
    }

    return 0;
}
